﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Xml;

using mvcForum.Core.Interfaces.AddOns;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Specifications;

namespace mvcForum.Core.Abstractions {

	public abstract class AddOnConfiguration<TAddOn> : IAddOnConfiguration<TAddOn> where TAddOn : IAddOn {
		private readonly AddOnConfiguration config;
		private readonly XmlDocument xml;
		private readonly XmlNode root;

		protected AddOnConfiguration(IRepository<AddOnConfiguration> configRepo) {
			String key = this.GetType().Name;
			this.config = configRepo.ReadOne(new AddOnConfigurationSpecifications.SpecificKey(key));
			if (this.config == null) {
				this.config = new AddOnConfiguration(key, "<Settings />");
				configRepo.Create(config);
			}

			this.xml = new XmlDocument();
			this.xml.LoadXml(this.config.Value);
			this.root = xml.SelectSingleNode("./*");
		}

		/// <summary>
		/// Method for getting an array of strings from the configuration
		/// </summary>
		/// <param name="key">The key of the configuration.</param>
		/// <returns>The list of strings for the given configuration.</returns>
		protected virtual IEnumerable<String> GetStrings(String key) {
			List<String> values = new List<String>();
			XmlNode keyNode = this.root.SelectSingleNode(key);
			if (keyNode != null) {
				foreach (XmlNode valueNode in keyNode.SelectNodes("Value")) {
					if (!values.Contains(valueNode.InnerText)) {
						values.Add(valueNode.InnerText);
					}
				}
			}
			return values;
		}

		/// <summary>
		/// Method for getting a collection of ints from the configuration.
		/// </summary>
		/// <param name="key">The key of the configuration.</param>
		/// <returns>The list of ints for the given configuration.</returns>
		protected virtual IEnumerable<Int32> GetCollection(String key) {
			IEnumerable<String> strings = this.GetStrings(key);

			List<Int32> values = new List<Int32>();
			foreach (String stringValue in strings) {
				Int32 value;
				if (Int32.TryParse(stringValue, out value)) {
					if (!values.Contains(value)) {
						values.Add(value);
					}
				}
			}
			return values;
		}

		/// <summary>
		/// Method for getting an integer value from the configuration.
		/// </summary>
		/// <param name="key">The key of the configuration.</param>
		/// <returns>The integer value from the given configuration.</returns>
		protected virtual Int32 GetInt32(String key) {
			if (this.root.Attributes.GetNamedItem(key) != null) {
				Int32 output;
				if (Int32.TryParse(this.root.Attributes.GetNamedItem(key).Value, out output)) {
					return output;
				}
			}
			this.Set(key, 0);
			return this.GetInt32(key);
		}

		/// <summary>
		/// Method for getting a string value from the configuration.
		/// </summary>
		/// <param name="key">The key of the configuration.</param>
		/// <returns>The string value from the given configuration.</returns>
		protected virtual String GetString(String key) {
			if (this.root.SelectSingleNode(key) != null) {
				return this.root.SelectSingleNode(key).InnerText;
			}
			this.Set(key, "");
			return this.GetString(key);
		}

		/// <summary>
		/// Method for getting a boolean value from the configuration
		/// </summary>
		/// <param name="key">The key of the configuration.</param>
		/// <returns>The boolean value from the given configuration.</returns>
		protected virtual Boolean GetBoolean(String key) {
			if (this.root.Attributes.GetNamedItem(key) != null) {
				Boolean output;
				if (Boolean.TryParse(this.root.Attributes.GetNamedItem(key).Value, out output)) {
					return output;
				}
			}
			this.Set(key, false);
			return this.GetBoolean(key);
		}

		protected virtual void Set(String key, IEnumerable<String> values) {
			XmlNode keyNode = this.root.SelectSingleNode(key);
			if (keyNode == null) {
				keyNode = xml.CreateElement(key);
				this.root.AppendChild(keyNode);
			}
			keyNode.RemoveAll();

			foreach (String value in values) {
				XmlNode valueNode = xml.CreateElement("Value");
				keyNode.AppendChild(valueNode);
				valueNode.AppendChild(xml.CreateCDataSection(value));
			}
			this.config.Value = this.xml.OuterXml;
		}

		protected virtual void Set(String key, IEnumerable<Int32> values) {
			this.Set(key, values.Select(v => v.ToString()));
		}

		protected virtual void Set(String key, Int32 value) {
			this.SetAttributeValue(key, value.ToString());
		}

		protected virtual void Set(String key, Boolean value) {
			this.SetAttributeValue(key, value.ToString());
		}

		protected virtual void Set(String key, String value) {
			this.SetNodeContent(key, value);
		}

		private void SetAttributeValue(String key, String value) {
			if (this.root.Attributes.GetNamedItem(key) == null) {
				this.root.Attributes.Append(xml.CreateAttribute(key));
			}
			this.root.Attributes.GetNamedItem(key).Value = value;
			this.config.Value = this.xml.OuterXml;
		}

		private void SetNodeContent(String key, String value) {
			if (this.root.SelectSingleNode(key) == null) {
				XmlNode keyNode = xml.CreateElement(key);
				this.root.AppendChild(keyNode);
			}

			this.root.SelectSingleNode(key).InnerText = value;
			this.config.Value = this.xml.OuterXml;
		}
	}
}